package org.orbis.flag;

import org.msgpack.MessagePackObject;
import org.msgpack.object.FloatType;
import org.msgpack.object.IntegerType;
import org.msgpack.object.RawType;
import org.orbis.constants.Constants;
import org.orbis.structure.ContentInfo;
import org.orbis.structure.range.FloatRangeTuple;
import org.orbis.structure.range.IntegerRangeTuple;
import org.orbis.structure.range.LongRangeTuple;

public class SearchFlags{
	private byte[] binaryFlags;
	private IntegerRangeTuple[] intRange;
	private LongRangeTuple[] longRange;
	private FloatRangeTuple[] floatRange;

	private SearchFlags(){}

	public SearchFlags(ContentInfo contentInfo){
		binaryFlags = new byte[contentInfo.binaryFlagNum()];
		intRange = new IntegerRangeTuple[contentInfo.integerFlagNum()];
		longRange = new LongRangeTuple[contentInfo.longFlagNum()];
		floatRange = new FloatRangeTuple[contentInfo.floatFlagNum()];
	}
	
	public boolean inquiryBinary(int index, boolean value) {
		return binaryFlags[index] == -1? true: binaryFlags[index] == 1? value: !value;
	}

	public void setBinaryFlags(int index, boolean isCare, boolean value){
//		if(index >= binaryFlags.length) return;
		if(!isCare) binaryFlags[index] = -1;
		else binaryFlags[index] = (byte)(value? 1: 0);
	}

	public void setBinaryFlags(int index, byte value){
//		if(index >= binaryFlags.length) return;
		if(value > 1 || value < 0) binaryFlags[index] = -1;
		binaryFlags[index] = value;
	}
	
	public boolean inquiryInt(int index, int value) {
		return intRange[index].isRange(value);
	}

	public void setIntFlags(int index, int type, int valueFrom, int valueTo){
//		if(index >= intRange.length) return;
		switch(type){
		case Constants.RANGE_GREATER:
			intRange[index] = IntegerRangeTuple.greaterThan(valueFrom);
			break;
		case Constants.RANGE_LESSOREQUAL:
			intRange[index] = IntegerRangeTuple.lessThan(valueFrom);
			break;
		case Constants.RANGE_EQUAL:
			intRange[index] = IntegerRangeTuple.equalValue(valueFrom);
			break;
		case Constants.RANGE_NOTEQUAL:
			intRange[index] = IntegerRangeTuple.notEqualValue(valueFrom);
			break;
		case Constants.RANGE_RANGE:
			intRange[index] = IntegerRangeTuple.range(valueFrom, valueTo);
			break;
		case Constants.RANGE_NOTRANGE:
			intRange[index] = IntegerRangeTuple.notRange(valueFrom, valueTo);
			break;
		default:
			intRange[index] = IntegerRangeTuple.dontCare();
		}
	}

	public boolean inquiryLong(int index, long value) {
		return longRange[index].isRange(value);
	}

	public void setLongFlags(int index, int type, long valueFrom, long valueTo){
//		if(index >= longRange.length) return;
		switch(type){
		case Constants.RANGE_GREATER:
			longRange[index] = LongRangeTuple.greaterThan(valueFrom);
			break;
		case Constants.RANGE_LESSOREQUAL:
			longRange[index] = LongRangeTuple.lessThan(valueFrom);
			break;
		case Constants.RANGE_EQUAL:
			longRange[index] = LongRangeTuple.equalValue(valueFrom);
			break;
		case Constants.RANGE_NOTEQUAL:
			longRange[index] = LongRangeTuple.notEqualValue(valueFrom);
			break;
		case Constants.RANGE_RANGE:
			longRange[index] = LongRangeTuple.range(valueFrom, valueTo);
			break;
		case Constants.RANGE_NOTRANGE:
			longRange[index] = LongRangeTuple.notRange(valueFrom, valueTo);
			break;
		default:
			longRange[index] = LongRangeTuple.dontCare();
		}
	}

	public boolean inquiryFloat(int index, float value) {
		return floatRange[index].isRange(value);
	}

	public void setFloatFlags(int index, int type, float valueFrom, float valueTo){
//		if(index >= floatRange.length) return;
		switch(type){
		case Constants.RANGE_GREATER:
			floatRange[index] = FloatRangeTuple.greaterThan(valueFrom);
			break;
		case Constants.RANGE_LESSOREQUAL:
			floatRange[index] = FloatRangeTuple.lessThan(valueFrom);
			break;
		case Constants.RANGE_EQUAL:
			floatRange[index] = FloatRangeTuple.equalValue(valueFrom);
			break;
		case Constants.RANGE_NOTEQUAL:
			floatRange[index] = FloatRangeTuple.notEqualValue(valueFrom);
			break;
		case Constants.RANGE_RANGE:
			floatRange[index] = FloatRangeTuple.range(valueFrom, valueTo);
			break;
		case Constants.RANGE_NOTRANGE:
			floatRange[index] = FloatRangeTuple.notRange(valueFrom, valueTo);
			break;
		default:
			floatRange[index] = FloatRangeTuple.dontCare();
		}
	}

	public byte[] getBinaryFlags(){
		return binaryFlags;
	}

	public IntegerRangeTuple[] getIntRange(){
		return intRange;
	}

	public LongRangeTuple[] getLongRange(){
		return longRange;
	}

	public FloatRangeTuple[] getFloatRange(){
		return floatRange;
	}

	public static final int HEADER = 'p' << 24 | 'f' << 16 | 'l' << 8 | 'g';

	public static SearchFlags unpack(MessagePackObject[] object, int index){
		SearchFlags flags = new SearchFlags();

		int header = object[index].asInt();
		if(header != HEADER) return null;
		flags.binaryFlags = object[index + 1].asByteArray();
		flags.intRange = new IntegerRangeTuple[object[index + 2].asInt()];
		flags.longRange = new LongRangeTuple[object[index + 3].asInt()];
		flags.floatRange = new FloatRangeTuple[object[index + 4].asInt()];

		int subtotal = index + 5;
		for(int i = 0; i < flags.intRange.length; i++)
			flags.intRange[i] =
				IntegerRangeTuple.fromMessage(object[3 * i + subtotal].asInt(),
					object[3 * i + subtotal + 1].asInt(), object[3 * i + subtotal + 2].asInt());

		subtotal += 3 * flags.intRange.length;
		for(int i = 0; i < flags.longRange.length; i++)
			flags.longRange[i] =
				LongRangeTuple.fromMessage(object[3 * i + subtotal].asLong(),
					object[3 * i + subtotal + 1].asLong(), object[3 * i + subtotal + 2].asInt());

		subtotal += 3 * flags.longRange.length;
		for(int i = 0; i < flags.floatRange.length; i++)
			flags.floatRange[i] =
				FloatRangeTuple.fromMessage(object[3 * i + subtotal].asFloat(),
					object[3 * i + subtotal + 1].asFloat(), object[3 * i + subtotal + 2].asInt());

		return flags;
	}

	public void pack(MessagePackObject[] object, int index){
		if(object.length < 5 + 3 * (intRange.length + longRange.length + floatRange.length) + index) return;
		object[index] = IntegerType.create(HEADER);
		object[index + 1] = RawType.create(binaryFlags);
		object[index + 2] = IntegerType.create(intRange.length);
		object[index + 3] = IntegerType.create(longRange.length);
		object[index + 4] = IntegerType.create(floatRange.length);

		int subtotal = index + 5;
		for(int i = 0; i < intRange.length; i++){
			object[3 * i + subtotal] = IntegerType.create(intRange[i].rangeFrom());
			object[3 * i + subtotal + 1] = IntegerType.create(intRange[i].rangeTo());
			object[3 * i + subtotal + 2] = IntegerType.create(intRange[i].type());
		}

		subtotal += 3 * intRange.length;
		for(int i = 0; i < longRange.length; i++){
			object[3 * i + subtotal] = IntegerType.create(longRange[i].rangeFrom());
			object[3 * i + subtotal + 1] = IntegerType.create(longRange[i].rangeTo());
			object[3 * i + subtotal + 2] = IntegerType.create(longRange[i].type());
		}

		subtotal += 3 * longRange.length;
		for(int i = 0; i < floatRange.length; i++){
			object[3 * i + subtotal] = FloatType.create(floatRange[i].rangeFrom());
			object[3 * i + subtotal + 1] = FloatType.create(floatRange[i].rangeTo());
			object[3 * i + subtotal + 2] = IntegerType.create(floatRange[i].type());
		}
	}
	
	public int packSize() {
		return 5 + 3 * (intRange.length + longRange.length + floatRange.length);
	}
	
	@Override
	public String toString(){
		StringBuffer buf = new StringBuffer();
		for(int i = 0; i < binaryFlags.length; i++){
			buf.append("Binary ");
			buf.append(i);
			buf.append(": ");
			buf.append(binaryFlags[i]);
			buf.append("\n");
		}
		
		for(int i = 0; i < intRange.length; i++){
			buf.append("Integer ");
			buf.append(i);
			buf.append(": ");
			switch(intRange[i].type()) {
			case Constants.RANGE_DONTCARE:
				buf.append("Don't care");
				break;
			case Constants.RANGE_EQUAL:
				buf.append("Equal to ");
				buf.append(intRange[i].rangeFrom());
				break;
			case Constants.RANGE_NOTEQUAL:
				buf.append("Not equal to ");
				buf.append(intRange[i].rangeFrom());
				break;
			case Constants.RANGE_GREATER:
				buf.append("Greater than or equal to (>=) ");
				buf.append(intRange[i].rangeFrom());
				break;
			case Constants.RANGE_LESSOREQUAL:
				buf.append("Less than or equal to (<=) ");
				buf.append(intRange[i].rangeTo());
				break;
			case Constants.RANGE_RANGE:
				buf.append(intRange[i].rangeFrom());
				buf.append(" <= x <= ");
				buf.append(intRange[i].rangeTo());
				break;
			case Constants.RANGE_NOTRANGE:
				buf.append(" x <= ");
				buf.append(intRange[i].rangeFrom());
				buf.append(", ");
				buf.append(intRange[i].rangeTo());
				buf.append(" <= x ");
			}
			buf.append("\n");
		}
		
		for(int i = 0; i < longRange.length; i++){
			buf.append("Long ");
			buf.append(i);
			buf.append(": ");
			switch(longRange[i].type()) {
			case Constants.RANGE_DONTCARE:
				buf.append("Don't care");
				break;
			case Constants.RANGE_EQUAL:
				buf.append("Equal to ");
				buf.append(longRange[i].rangeFrom());
				break;
			case Constants.RANGE_NOTEQUAL:
				buf.append("Not equal to ");
				buf.append(longRange[i].rangeFrom());
				break;
			case Constants.RANGE_GREATER:
				buf.append("Greater than or equal to (>=) ");
				buf.append(longRange[i].rangeFrom());
				break;
			case Constants.RANGE_LESSOREQUAL:
				buf.append("Less than or equal to (<=) ");
				buf.append(longRange[i].rangeTo());
				break;
			case Constants.RANGE_RANGE:
				buf.append(longRange[i].rangeFrom());
				buf.append(" <= x <= ");
				buf.append(longRange[i].rangeTo());
				break;
			case Constants.RANGE_NOTRANGE:
				buf.append(" x <= ");
				buf.append(longRange[i].rangeFrom());
				buf.append(", ");
				buf.append(longRange[i].rangeTo());
				buf.append(" <= x ");
			}
			buf.append("\n");
		}
		
		for(int i = 0; i < floatRange.length; i++){
			buf.append("Float ");
			buf.append(i);
			buf.append(": ");
			switch(floatRange[i].type()) {
			case Constants.RANGE_DONTCARE:
				buf.append("Don't care");
				break;
			case Constants.RANGE_EQUAL:
				buf.append("Equal to ");
				buf.append(floatRange[i].rangeFrom());
				break;
			case Constants.RANGE_NOTEQUAL:
				buf.append("Not equal to ");
				buf.append(floatRange[i].rangeFrom());
				break;
			case Constants.RANGE_GREATER:
				buf.append("Greater than or equal to (>=) ");
				buf.append(floatRange[i].rangeFrom());
				break;
			case Constants.RANGE_LESSOREQUAL:
				buf.append("Less than or equal to (<=) ");
				buf.append(floatRange[i].rangeTo());
				break;
			case Constants.RANGE_RANGE:
				buf.append(floatRange[i].rangeFrom());
				buf.append(" <= x <= ");
				buf.append(floatRange[i].rangeTo());
				break;
			case Constants.RANGE_NOTRANGE:
				buf.append(" x <= ");
				buf.append(floatRange[i].rangeFrom());
				buf.append(", ");
				buf.append(floatRange[i].rangeTo());
				buf.append(" <= x ");
			}
			buf.append("\n");
		}
		
		return buf.toString();
	}
}
